// Package code: 字节码包，定义字节码相关格式和辅助工具信息
package code

import (
	"bytes"
	"encoding/binary"
	"fmt"
)

// Instructions 字节码指令 - 包含字节码操作码，字节码操作数. 操作码一般是一个字节，操作数可能有多个
type Instructions []byte

func (ins Instructions) String() string {
	var out bytes.Buffer

	i := 0
	for i < len(ins) {
		def, err := Lookup(ins[i])
		if err != nil {
			_, _ = fmt.Fprintf(&out, "ERROR: %s\n", err)
			continue
		}
		operands, readLen := ReadOperands(def, ins[i+1:])
		_, _ = fmt.Fprintf(&out, "%04d %s\n", i, ins.fmtInstruction(def, operands))

		i += 1 + readLen
	}

	return out.String()
}

// fmtInstruction 指令内容字符串格式化
func (ins Instructions) fmtInstruction(def *Definition, operands []int) string {
	operandCnt := len(def.OperandWidths)
	if len(operands) != operandCnt {
		return fmt.Sprintf("ERROR: operand len %d does not match defined %d \n", len(operands), operandCnt)
	}
	switch operandCnt {
	case 0:
		return fmt.Sprintf("%s", def.Name)
	case 1:
		return fmt.Sprintf("%s %d", def.Name, operands[0])
	case 2:
		return fmt.Sprintf("%s %d %d", def.Name, operands[0], operands[1])
	}
	return fmt.Sprintf("ERROR: unhandled operandCount for %s\n", def.Name)
}

// Opcode 操作码
type Opcode byte

const (
	OpConstant    Opcode = iota // OpConstant 常量操作码 - 每个常量操作码都会有一个操作数，使用操作数作为索引去常量池中查询常量并将其压栈
	OpPop                       // OpPop 弹栈操作码 - 将虚拟机栈顶部元素弹栈，它没有操作数，只是一个单字节操作码
	OpAdd                       // OpAdd 加法操作码 - 将虚拟机栈顶部两个元素弹栈，并相加，然后将结果压栈. 它没有操作数, 只是一个单字节操作码.
	OpSub                       // OpSub 减法操作码 - 将虚拟机栈顶部两个元素弹栈，并相减，然后将结果压栈，它没有操作数，只是一个单字节操作码
	OpMul                       // OpMul 乘法操作码 - 将虚拟机栈顶部两个元素弹栈，并相乘，然后将结果压栈. 它没有操作数，只是一个单字节操作码
	OpDiv                       // OpDiv 除法操作码 - 将虚拟机栈顶部两个元素弹栈，并相除，然后将结果压栈. 它没有操作数，只是一个单字节操作码
	OpTrue                      // OpTrue 布尔常量操作码 - 将 true 压入虚拟机栈顶. 它没有操作数，只是一个单字节操作码
	OpFalse                     // OpFalse 布尔常量操作码 - 将 false 压入虚拟机栈顶. 它没有操作数，只是一个单字节操作码
	OpEqual                     // OpEqual 等于比较操作码 - 将虚拟机栈顶部两个元素弹栈，并进行等于比较，然后将结果压栈. 它没有操作数，只是一个单字节操作码
	OpNotEqual                  // OpNotEqual 不等于比较操作码 - 将虚拟机栈顶部两个元素弹栈，并进行不等于比较，然后将结果压栈. 它没有操作数，只是一个单字节操作码
	OpGreaterThan               // OpGreaterThan 大于比较操作码 - 将虚拟机栈顶部两个元素弹栈，并进行大于比较，然后将结果压栈. 它没有操作数，只是一个单字节操作码. 不考虑增加 小于 因为可以通过编译器优化，对代码进行重排序来将 1 < 2 转为 2 > 1
	OpMinus                     // OpMinus 负号操作码 - 将虚拟机栈顶部元素弹栈, 并取负, 然后将结果压栈. 它没有操作数，只是一个单字节操作码
	OpBang                      // OpBang 取反操作码 - 将虚拟机栈顶部元素弹栈, 并取反, 然后将结果压栈. 它没有操作数，只是一个单字节操作码
	OpNot                       // OpNot 非运算操作码 - 将虚拟机栈顶部元素弹栈, 并进行非运算操作, 然后将结果压栈. 它没有操作数，只是一个单字节操作码. 有1出0，有0出1 ( ~1 )

	OpAnd      // OpAnd 与运算操作码 - 将虚拟机栈顶部两个元素弹栈, 并进行与运算操作, 然后将结果压栈. 它没有操作数，只是一个单字节操作码. 有0出0，全1出1 ( 1 & 1 )
	OpOr       // OpOr 或运算操作码 - 将虚拟机栈顶部两个元素弹栈, 并进行或运算操作, 然后将结果压栈. 它没有操作数，只是一个单字节操作码. 有1出1，全0出0 ( 1 | 1 )
	OpXor      // OpXor 异或运算操作码 - 将虚拟机栈顶部两个元素弹栈, 并进行异或运算操作, 然后将结果压栈. 它没有操作数，只是一个单字节操作码. 相异为1，相同为0 (1 ^ 1)
	OpLShift   // OpLShift 左移运算操作码 - 将虚拟机栈顶部两个元素弹栈, 并进行左移运算操作, 然后将结果压栈. 它没有操作数，只是一个单字节操作码. ( 2 << 1 ==> 2 左移 1 位，右值 必须大于 0 )
	OpRShift   // OpRShift 右移运算操作码 - 将虚拟机栈顶部两个元素弹栈, 并进行右移运算操作, 然后将结果压栈. 它没有操作数，只是一个单字节操作码. ( 2 >> 1 ==> 2 右移 1 位. 右值 必须大于 0 )
	OpURShift  // OpURShift 无符号右移运算操作码 - 将虚拟机栈顶部两个元素弹栈, 并进行右移运算操作, 然后将结果压栈. 它没有操作数，只是一个单字节操作码. ( 1 >>> 1 ==> 2 无符号右移 1 位. 右值 必须大于 0 )
	OpLogicAnd // OpLogicAnd 逻辑与操作码 - 将虚拟机栈顶部两个元素弹栈, 并进行逻辑与判断, 然后将结果压栈. 它没有操作数，只是一个单字节操作码. 两个条件都为真，则返回true，否则返回false
	OpLogicOr  // OpLogicOr 逻辑或操作码 - 将虚拟机栈顶部两个元素弹栈, 并进行逻辑或判断, 然后将结果压栈. 它没有操作数，只是一个单字节操作码. 两个条件都为真，只要一个条件为真，则返回true，否则返回false

	OpJumpNotTruthy // OpJumpNotTruthy 逻辑跳转 - 非真跳转操作码 - 在虚拟机栈顶部内容不为真或者为空的时候进行跳转. 它有一个操作数，指明跳转的偏移量
	OpJump          // OpJump 跳转操作码 - 无条件跳转，它有一个操作数, 指明跳转的偏移量

	OpNull // OpNull Null值操作码 - 将 Null 压入虚拟机栈顶, 它只是一个单字节操作码, 它的操作码隐含了操作数

	OpSetGlobal // OpSetGlobal 设置全局变量 - 会有一个两字节的操作数，使用操作数作为索引将变量值存储到全局变量表中
	OpGetGlobal // OpGetGlobal 获取全局变量 - 会有一个两字节的操作数，使用操作数作为索引从全局变量表中获取值
	OpSetLocal  // OpSetLocal 设置局部变量 - 会有一个单字节的操作数，使用操作数作为索引将变量值存储到局部变量表中
	OpGetLocal  // OpGetLocal 获取局部变量 - 会有一个单字节的操作数，使用操作数作为索引从局部变量表中获取值

	OpArray // OpArray 数组操作码 - 会有一个两字节的操作数, 使用操作数标识这个数组中的元素的个数. 编译数组的时候，先编译它所有的元素，当虚拟机执行OpArray指令时，它从栈中弹出N个元素，构建一个*object.Array，然后将其压栈
	OpHash  // OpHash 哈希表操作码 - 会有一个两字节的操作数, 使用操作数标识这个哈希表中的键和值的数量. 编译数组的时候，先编译它所有的键值对元素，当虚拟机执行OpHash指令时，它从虚拟机栈中弹出 键和值的数量 个元素，构建一个 *object.Hash 然后将其压栈

	OpIndex // OpIndex 索引操作码 - 没有操作数. 但是虚拟机栈栈顶需要有两个元素: 1. 索引值 2. 被索引的对象. 当虚拟机执行OpIndex指令时, 它从栈中弹出这两个元素, 执行索引操作

	OpCall // OpCall 函数调用操作码 - 一个单字节的操作数. 指明被调用函数的参数个数. 当虚拟机执行 OpCall 指令时, 会从虚拟机栈栈顶弹出被调用的函数及其参数进行执行
	//         一般而言，都是通过 OpConstant 指令将 被调用的函数压栈，通过 OpCall 指令执行栈顶的函数
	OpReturnValue // OpReturnValue 返回值操作码 - 没有操作数，但是返回值必须要位于虚拟机栈栈顶. 当虚拟机执行 OpReturnValue 指令时, 会从虚拟机栈栈顶弹出返回值
	OpReturn      // OpReturn 返回操作码 - 没有操作数. 当虚拟机执行 OpReturn 指令时，它会中止当前函数执行，回到调用这个函数之前的逻辑
	OpGetBuiltin  // OpGetBuiltin 获取内置函数操作码 - 一个单字节的操作数,
	OpClosure     // OpClosure 闭包类型操作码 - 有两个操作数，一个两字节，一个单字节，当虚拟机执行 OpClosure 指令时, 它会将指定的 *object.CompiledFunction 封装到 *object.Closure 中
	// 第一个两字节操作数是常量索引,用于指定在常量池中的哪个位置找到要转换为闭包的 *object.CompiledFunction, 第二个单字节操作数是用于指定栈中有多少个自由变量需要存储到 *object.Closure 的 Free 字段中
	OpGetFree        // OpGetFree 获取自由变量操作码 - 一个操作数
	OpCurrentClosure // OpCurrentClosure 获取当前正在执行的闭包函数 - 没有操作数
)

// Definition 操作码描述信息, 方便查找操作码有多少个操作数以及它们的助记符是什么
type Definition struct {
	Name          string // 操作码助记符名称
	OperandWidths []int  // 每个操作数占用的字节数
}

// definitions 操作码信息映射map，操作码为key，其描述信息为value
var definitions = map[Opcode]*Definition{
	OpConstant:    {"OpConstant", []int{2}},   // 常量操作码, 他的助记符名称为 OpConstant，只有一个两字节宽的操作数
	OpPop:         {"OpPop", []int{}},         // 弹栈操作码 - 将虚拟机顶部元素弹栈，它没有操作数，只是一个单字节操作码
	OpAdd:         {"OpAdd", []int{}},         // 加法操作码 - 让虚拟机顶部两个元素弹栈，并相加，然后将结果压栈. 它没有操作数, 只是一个单字节操作码.
	OpSub:         {"OpSub", []int{}},         // OpSub 减法操作码 - 让虚拟机顶部两个元素弹栈，并相减，然后将结果压栈，它没有操作数，只是一个单字节操作码
	OpMul:         {"OpMul", []int{}},         // OpMul 乘法操作码 - 让虚拟机顶部两个元素弹栈，并相乘，然后将结果压栈. 它没有操作数，只是一个单字节操作码
	OpDiv:         {"OpDiv", []int{}},         // OpDiv 除法操作码 - 让虚拟机顶部两个元素弹栈，并相除，然后将结果压栈. 它没有操作数，只是一个单字节操作码
	OpTrue:        {"OpTrue", []int{}},        // OpTrue 布尔常量操作码 - 将 true 压入虚拟机栈顶. 它没有操作数，只是一个单字节操作码
	OpFalse:       {"OpFalse", []int{}},       // OpFalse 布尔常量操作码 - 将 false 压入虚拟机栈顶. 它没有操作数，只是一个单字节操作码
	OpEqual:       {"OpEqual", []int{}},       // OpEqual 等于比较操作码 - 将虚拟机栈顶部两个元素弹栈，并进行等于比较，然后将结果压栈. 它没有操作数，只是一个单字节操作码
	OpNotEqual:    {"OpNotEqual", []int{}},    // OpNotEqual 不等于比较操作码 - 将虚拟机栈顶部两个元素弹栈，并进行不等于比较，然后将结果压栈. 它没有操作数，只是一个单字节操作码
	OpGreaterThan: {"OpGreaterThan", []int{}}, // OpGreaterThan 大于比较操作码 - 将虚拟机栈顶部两个元素弹栈，并进行大于比较，然后将结果压栈. 它没有操作数，只是一个单字节操作码
	OpMinus:       {"OpMinus", []int{}},       // OpMinus 负号操作码 - 将虚拟机栈顶部元素弹栈, 并取负, 然后将结果压栈. 它没有操作数，只是一个单字节操作码
	OpBang:        {"OpBang", []int{}},        // OpBang 取反操作码 - 将虚拟机栈顶部元素弹栈, 并取反, 然后将结果压栈. 它没有操作数，只是一个单字节操作码
	OpNot:         {"OpNot", []int{}},         // OpNot 非运算操作码 - 将虚拟机栈顶部元素弹栈, 并进行非运算操作, 然后将结果压栈. 它没有操作数，只是一个单字节操作码. 有1出0，有0出1 ( ~1 )

	OpAnd:      {"OpAnd", []int{}},      // OpAnd 与运算操作码 - 将虚拟机栈顶部两个元素弹栈, 并进行与运算操作, 然后将结果压栈. 它没有操作数，只是一个单字节操作码. 有0出0，全1出1 ( 1 & 1 )
	OpOr:       {"OpOr", []int{}},       // OpOr 或运算操作码 - 将虚拟机栈顶部两个元素弹栈, 并进行或运算操作, 然后将结果压栈. 它没有操作数，只是一个单字节操作码. 有1出1，全0出0 ( 1 | 1 )
	OpXor:      {"OpXor", []int{}},      // OpXor 异或运算操作码 - 将虚拟机栈顶部两个元素弹栈, 并进行异或运算操作, 然后将结果压栈. 它没有操作数，只是一个单字节操作码. 相异为1，相同为0 (1 ^ 1)
	OpLShift:   {"OpLShift", []int{}},   // OpLShift 左移运算操作码 - 将虚拟机栈顶部两个元素弹栈, 并进行左移运算操作, 然后将结果压栈. 它没有操作数，只是一个单字节操作码. ( 2 << 1 ==> 2 左移 1 位，右值 必须大于 0 )
	OpRShift:   {"OpRShift", []int{}},   // OpRShift 右移运算操作码 - 将虚拟机栈顶部两个元素弹栈, 并进行右移运算操作, 然后将结果压栈. 它没有操作数，只是一个单字节操作码. ( 2 >> 1 ==> 2 右移 1 位. 右值 必须大于 0 )
	OpURShift:  {"OpURShift", []int{}},  // OpURShift 无符号右移运算操作码 - 将虚拟机栈顶部两个元素弹栈, 并进行右移运算操作, 然后将结果压栈. 它没有操作数，只是一个单字节操作码. ( 1 >>> 1 ==> 2 无符号右移 1 位. 右值 必须大于 0 )
	OpLogicAnd: {"OpLogicAnd", []int{}}, // OpLogicAnd 逻辑与操作码 - 将虚拟机栈顶部两个元素弹栈, 并进行逻辑与判断, 然后将结果压栈. 它没有操作数，只是一个单字节操作码. 两个条件都为真，则返回true，否则返回false
	OpLogicOr:  {"OpLogicOr", []int{}},  // OpLogicOr 逻辑或操作码 - 将虚拟机栈顶部两个元素弹栈, 并进行逻辑或判断, 然后将结果压栈. 它没有操作数，只是一个单字节操作码. 两个条件都为真，只要一个条件为真，则返回true，否则返回false

	OpJumpNotTruthy: {"OpJumpNotTruthy", []int{2}}, // OpJumpNotTruthy 跳转操作码 - 非真跳转 - 在虚拟机栈顶部内容不为真或者为空的时候进行跳转. 它有一个两字节的操作数，指明跳转的偏移量
	OpJump:          {"OpJump", []int{2}},          // OpJump 跳转操作码 - 无条件跳转，它有一个两字节的操作数, 指明跳转的偏移量

	OpNull: {"OpNull", []int{}}, // OpNull Null值操作码 - 将 Null 压入虚拟机栈顶, 它只是一个单字节操作码, 它的操作码隐含了操作数

	OpSetGlobal: {"OpSetGlobal", []int{2}}, // OpSetGlobal 设置全局变量 - 会有一个双字节的操作数，使用操作数作为索引将变量值存储到全局变量表中
	OpGetGlobal: {"OpGetGlobal", []int{2}}, // OpGetGlobal 获取全局变量 - 会有一个双字节的操作数，使用操作数作为索引从全局变量表中获取值
	OpSetLocal:  {"OpSetLocal", []int{1}},  // OpSetLocal 设置局部变量 - 会有一个单字节的操作数，使用操作数作为索引将变量值存储到局部变量表中
	OpGetLocal:  {"OpGetLocal", []int{1}},  // OpGetLocal 获取局部变量 - 会有一个单字节的操作数，使用操作数作为索引从局部变量表中获取值

	OpArray: {"OpArray", []int{2}}, // OpArray 数组操作码 - 会有一个两字节的操作数, 使用操作数标识这个数组中的元素的个数. 编译数组的时候，先编译它所有的元素，当虚拟机执行OpArray指令时，它从栈中弹出N个元素，构建一个*object.Array，然后将其压栈
	OpHash:  {"OpHash", []int{2}},  // OpHash 哈希表操作码 - 会有一个两字节的操作数, 使用操作数标识这个哈希表中的键和值的数量. 编译数组的时候，先编译它所有的键值对元素，当虚拟机执行OpHash指令时，它从虚拟机栈中弹出 键和值的数量 个元素，构建一个 *object.Hash 然后将其压栈

	OpIndex: {"OpIndex", []int{}}, // OpIndex 索引操作码 - 没有操作数。但是虚拟机栈栈顶需要有两个元素: 1. 索引值 2. 被索引的对象. 当虚拟机执行OpIndex指令时, 它从栈中弹出这两个元素, 执行索引操作

	OpCall: {"OpCall", []int{1}}, // OpCall 函数调用操作码 - 一个单字节的操作数. 指明被调用函数的参数个数. 当虚拟机执行 OpCall 指令时, 会从虚拟机栈栈顶弹出被调用的函数及其参数进行执行
	//         一般而言，都是通过 OpConstant 指令将 被调用的函数压栈，通过 OpCall 指令执行栈顶的函数
	OpReturnValue: {"OpReturnValue", []int{}}, // OpReturnValue 返回操作码 - 没有操作数，但是返回值必须要位于虚拟机栈栈顶. 当虚拟机执行 OpReturnValue 指令时, 会从虚拟机栈栈顶弹出返回值
	OpReturn:      {"OpReturn", []int{}},      // OpReturn 返回操作码 - 没有操作数. 当虚拟机执行 OpReturn 指令时，它会中止当前函数执行，回到调用这个函数之前的逻辑
	OpGetBuiltin:  {"OpGetBuiltin", []int{1}}, // OpGetBuiltin 获取内置函数操作码 - 一个单字节的操作数,
	OpClosure:     {"OpClosure", []int{2, 1}}, // OpClosure 闭包类型操作码 - 有两个操作数，一个两字节，一个单字节，当虚拟机执行 OpClosure 指令时, 它会将指定的 *object.CompiledFunction 封装到 *object.Closure 中
	// 第一个两字节操作数是常量索引,用于指定在常量池中的哪个位置找到要转换为闭包的 *object.CompiledFunction, 第二个单字节操作数是用于指定栈中有多少个自由变量需要存储到 *object.Closure 的 Free 字段中
	OpGetFree:        {"OpGetFree", []int{1}},       // OpGetFree 获取自由变量操作码 - 一个操作数
	OpCurrentClosure: {"OpCurrentClosure", []int{}}, // OpCurrentClosure 获取当前正在执行的闭包函数 - 没有操作数
}

// Lookup 操作码描述信息查找方法, 根据传入的操作码去 definitions 中查询对应的 操作码描述信息 Definition 数据
func Lookup(op byte) (*Definition, error) {
	if def, ok := definitions[Opcode(op)]; ok {
		return def, nil
	}
	return nil, fmt.Errorf("opcode %d undefined", op)
}

// Make 根据传入的操作码和操作数，快速创建包含操作码和操作数的字节码指令
func Make(op Opcode, operands ...int) []byte {
	def, ok := definitions[op]
	// 如果这个操作码没有被定义，那么直接返回空数组即可
	if !ok {
		return []byte{}
	}
	// 计算字节码指令长度, 初始为1，因为字节码的操作码是一字节长度
	instructionLen := 1
	for _, w := range def.OperandWidths {
		instructionLen += w
	}
	// 字节码指令
	instruction := make([]byte, instructionLen)
	// 操作码
	instruction[0] = byte(op)
	// 偏移量
	offset := 1
	// 操作数
	for i, o := range operands {
		width := def.OperandWidths[i]
		switch width {
		case 2:
			binary.BigEndian.PutUint16(instruction[offset:], uint16(o))
		case 1:
			instruction[offset] = byte(o)
		}
		offset += width
	}

	return instruction
}

// ReadOperands 根据传入的 操作码 和 指令 信息，对指令进行解码
func ReadOperands(def *Definition, ins Instructions) ([]int, int) {
	operands := make([]int, len(def.OperandWidths))
	offset := 0

	for i, width := range def.OperandWidths {
		switch width {
		case 2:
			operands[i] = int(ReadUint16(ins[offset:]))
		case 1:
			operands[i] = int(ReadUint8(ins[offset:]))
		}
		offset += width
	}

	return operands, offset
}

// ReadUint16 读取一个uint16
func ReadUint16(ins Instructions) uint16 {
	return binary.BigEndian.Uint16(ins)
}

func ReadUint8(ins Instructions) uint8 {
	return ins[0]
}
